package com.appcheck.recording;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.TreeSet;

import android.app.Instrumentation;
import android.os.Build;
import android.os.SystemClock;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.DatePicker;
import android.widget.EditText;
import android.widget.ExpandableListView;
import android.widget.Spinner;
import android.widget.TimePicker;
import android.widget.ZoomControls;

import com.appcheck.recording.action.AbstractAction;
import com.appcheck.recording.handler.AbstractActionHandler;
import com.appcheck.recording.handler.impl.EditTextGrouper;
import com.appcheck.recording.handler.impl.OneToOneGrouper;
import com.appcheck.recording.handler.impl.ScrollGrouper;
import com.appcheck.recording.handler.impl.SpinnerGrouper;
import com.appcheck.recording.handler.impl.TouchGrouper;
import com.appcheck.recording.listener.impl.ChildClickListener;
import com.appcheck.recording.listener.impl.ClickListener;
import com.appcheck.recording.listener.impl.DateChangedListener;
import com.appcheck.recording.listener.impl.EditorActionListener;
import com.appcheck.recording.listener.impl.FocusChangeListener;
import com.appcheck.recording.listener.impl.GlobalLayoutListener;
import com.appcheck.recording.listener.impl.GrouperClickListener;
import com.appcheck.recording.listener.impl.ItemClickListener;
import com.appcheck.recording.listener.impl.ItemLongClickListener;
import com.appcheck.recording.listener.impl.ItemSelectedListener;
import com.appcheck.recording.listener.impl.LongClickListener;
import com.appcheck.recording.listener.impl.RecordingTextWatcher;
import com.appcheck.recording.listener.impl.ScrollListener;
import com.appcheck.recording.listener.impl.TimeChangedListener;
import com.appcheck.recording.listener.impl.TouchListener;
import com.appcheck.recording.utils.Logger;
import com.robotium.solo.Solo;


public final class ListenerManager {
	private static ListenerManager listenerManager;
	private static String windowManagerName;
	private static Class windowManager;
	/**
	 * 记录所有的view，如果changesInLayout变化则重新加载屏幕中view的Listener
	 * 如果view在这个Set中则不更新Listener
	 */
	private Set viewsSet;
	/**
	 * 需要有一个set用来就来没有触发changesInLayout变化时需要更新Listener的view
	 * 这里面的view的listener可以手动更新
	 */
	private Set manualChangeViewSet;
	private AbstractActionHandler handlerChain;
	private Instrumentation instrumentation;
	private int changesInLayout;
	private int viewsCount;
	private boolean flag;//配合layoutChangedListener来确定是否需要调用AddListenersForViewsTask
	private View focusedView;
	private Solo solo;
	
	public static ListenerManager getListenerManager(){
		if(listenerManager == null){
			listenerManager = new ListenerManager();
		}
		return listenerManager;
	}
	
	protected ListenerManager(){
		this.viewsSet = new TreeSet();
		
		this.handlerChain = new EditTextGrouper(new SpinnerGrouper (new TouchGrouper(new ScrollGrouper(new OneToOneGrouper(null)))));
	}
	
	public final void setInstrumentation(Instrumentation instrumentation){
		this.instrumentation = instrumentation;
	}
	
	public final synchronized void init(){
		clean();
		if(this.instrumentation != null){
			this.instrumentation.runOnMainSync(new AddListenersForViewsTask());
			return;
		}
		Logger.WARN("Instrumentation not initialized by setting listeners");
	}
	
	public final void clean(){
		this.changesInLayout = 0;
		this.flag = false;
	}
	
	public final void setFlagTrue(){
		//Logger.DEBUG("Set ListenerManager.flag = true");
		this.flag = true;
	}
	
	public final View getFocusedView(){
		return  this.focusedView;
	}
	
	/**
	 * 给不是ViewGroup的View添加监听器
	 * @param paramView 当前View
	 * @param paramBoolean 判断是否给当前View添加监听器
	 * @param preViewList 从根节点遍历到这个View的过程中遇到的AdapterView到这个View之间记录的所有ViewGroup(包括第一个AdapterView，<br>
	 * 不包括AdpaterView里面还包含AdapterView的情况，这样生成的测试代码就会有问题)
	 * @param isManual 表示是不是手动调用，如果不是则表示是layoutListener调用的
	 * @return 表示是否给子View添加监听器， true表示添加，false表示不参加
	 */
	public boolean setListenersForSingleView(View paramView, boolean paramBoolean, PreViewManager preViewManager, boolean isManual){
		if(!isManual){
			if(this.viewsSet.contains(Integer.valueOf(paramView.hashCode()))){
				//Logger.INFO(paramView.getClass().getSimpleName() + " is in the view set");
				return paramBoolean;
			}
		}else{
			if(!manualChangeViewSet.contains(paramView.hashCode())){
				//Logger.INFO(paramView.getClass().getSimpleName() + " is not in the manual view set");
				return paramBoolean;
			}
		}
		//系统自己创建的View是-1
		Logger.DEBUG(paramView.getClass().getSimpleName() + " , " + String.valueOf(paramView.getId()) + " paramBoolean " + paramBoolean);
//	    if(preViewList == null) {
//	    	Logger.DEBUG("no preViewList");
//	    }else{
//	    	Logger.DEBUG(String.valueOf(preViewList.size()));
//	    }
		paramView.setOnTouchListener(new TouchListener(paramView, preViewManager));
		if(paramBoolean){
			if(paramView.getClass().getName().equals("com.android.internal.policy.impl.PhoneWindow$DecorView")){
				//Logger.DEBUG(paramView.toString() + " is com.android.internal.policy.impl.PhoneWindow$DecorView.");
				paramView.getViewTreeObserver().addOnGlobalLayoutListener(new GlobalLayoutListener());
				return true;
			}
			if (paramView instanceof EditText){
				//Logger.DEBUG(paramView.toString() + " is EditText.");
				((EditText)paramView).setOnEditorActionListener(new EditorActionListener((EditText)paramView));
		        ((EditText)paramView).addTextChangedListener(new RecordingTextWatcher());
		        paramView.setOnFocusChangeListener(new FocusChangeListener(paramView));
		        return false;
			}
			if ((paramView instanceof DatePicker)){
				//Logger.DEBUG(paramView.toString() + " is DatePicker.");
				DatePicker datePicker = (DatePicker)paramView;
				datePicker.init(datePicker.getYear(), datePicker.getMonth(), datePicker.getDayOfMonth(), new DateChangedListener(datePicker));
				return false;
			}
			if ((paramView instanceof TimePicker)){
				//Logger.DEBUG(paramView.toString() + " is TimePicker.");
		        ((TimePicker)paramView).setOnTimeChangedListener(new TimeChangedListener((TimePicker)paramView));
		        return false;
			}
			if ((paramView instanceof AdapterView)){
				if((paramView instanceof ExpandableListView)){
					//Logger.DEBUG(paramView.toString() + " is ExpandableListView.");
			        ((ExpandableListView)paramView).setOnChildClickListener(new ChildClickListener(paramView));
			        ((ExpandableListView)paramView).setOnGroupClickListener(new GrouperClickListener(paramView));
				}
				// Spinner没有OnItemClickListener
				if (!(paramView instanceof Spinner)){
					//Logger.DEBUG(paramView.toString() + " is not Spinner.");
					((AdapterView)paramView).setOnItemClickListener(new ItemClickListener(paramView));
		            ((AdapterView)paramView).setOnItemLongClickListener(new ItemLongClickListener(paramView));
		        }
				if ((paramView instanceof Spinner)){
					//Logger.DEBUG(paramView.toString() + " is Spinner.");
					((AdapterView)paramView).setOnItemSelectedListener(new ItemSelectedListener(paramView));
				}
				if ((paramView instanceof AbsListView)){
					try{
						//Logger.DEBUG(paramView.toString() + " is AbsListView.");
						((AbsListView)paramView).setOnScrollListener(new ScrollListener((AbsListView)paramView));
					}catch (Exception exception){
						Logger.ERROR("Recorder was not able to set OnScrollListener", exception);
					}
				}
				/*adapterView内部的view也应该访问,测试io.appium.android.apis.ApiDemos发现这里即使为里面的子view设置回调,
				 * 点击ListView的item的时候也只会调用itemClickListener而不会触发子view的clickListener
				 */
				return true;
			}
			if((paramView instanceof ZoomControls)){
				//Logger.DEBUG(paramView.toString() + " is ZoomControls.");
		        ((ZoomControls)paramView).setOnZoomInClickListener(new ClickListener(paramView));
		        ((ZoomControls)paramView).setOnZoomOutClickListener(new ClickListener(paramView));
		        
		        return false;
			}
			if (((paramView.isClickable()) || (paramView.isLongClickable())) && (!paramView.getClass().getName().equals("com.android.internal.view.menu.ActionMenuPresenter$OverflowMenuButton"))){
		        try{
		          if (paramView.isClickable()) {
		        	//Logger.DEBUG(paramView.toString() + " is clickable.");
		            paramView.setOnClickListener(new ClickListener(paramView, preViewManager));
		          }
		          if (paramView.isLongClickable()) {
		            paramView.setOnLongClickListener(new LongClickListener(paramView, preViewManager));
		          }
		        }
		        catch (RuntimeException localRuntimeException) {
		        	Logger.ERROR("RuntimeException",localRuntimeException);
		        }
		        return paramBoolean;
		     }
			//Logger.DEBUG(paramView.toString() + " is not one of above.");
			return paramBoolean;
		}
		return false;
	}
	/**
	 * 从根节点遍历，手动为子View添加Listener，这种情况适用于LayoutListener捕捉不到view的变化的情况<br>
	 * （例如：AdapterView的滑动，此时造成里面的view的重复利用，可能会覆盖原来的listener）
	 * @param paramViewGroup 当前ViewGroup，如果为AdapterView，则preViewIdManager也会包含这个AdapterView.
	 * @param paramBoolean
	 * @param preViewIdManager 如果为null，表示从根节点到这个ViewGroup为止没有AdpaterView，<br>
	 * 如果非空，记录记录从当前ViewGroup的父AdapterView到当前ViewGroup的所有中间的ViewGroup.(其中包括第一个AdpaterView)<br>
	 */
	public void setListenersManuallyFromRoot(ViewGroup paramViewGroup, boolean paramBoolean, PreViewManager preViewManager){
		for(int k = 0; k < paramViewGroup.getChildCount(); k++){
			View view = paramViewGroup.getChildAt(k);
			if(preViewManager != null && preViewManager.getIdNameList().size() == 1){
				preViewManager.setIndexInAdapterView(view);
			}
			boolean bool = setListenersForSingleView(view, paramBoolean, preViewManager, true);
			if(view instanceof ViewGroup){
				if(preViewManager != null){
					//前面有AdapterView，直接插入到列表中
					preViewManager.pushViewIdName(view);
					setListenersManuallyFromRoot((ViewGroup)view, bool, preViewManager);
					preViewManager.popViewIdName();
				}else{
					//如果是AdapterView需要记录，传入递归参数
					if(view instanceof AdapterView){
						preViewManager = new PreViewManager((AdapterView)view);
						setListenersManuallyFromRoot((ViewGroup)view, bool, preViewManager);
						preViewManager = null;
					}else{
						setListenersManuallyFromRoot((ViewGroup)view, bool, null);
					}
				}
			}
			
		}
	}
	/**
	 * 从根节点递归调用为ViewGroup添加Listener.</br>
	 * 最后一个参数的作用是如果遇到AdapterView，记录从这个AdapterView到后面目标View的所有中间的ViewGroup.
	 * @param paramViewGroup 当前ViewGroup，如果为AdapterView，则preViewIdManager也会包含这个AdapterView.
	 * @param paramBoolean
	 * @param paramSet1 新的viewSet
	 * @param paramSet2 新的manualChangeViewSet
	 * @param preViewIdManager 如果为null，表示从根节点到这个ViewGroup为止没有AdpaterView，<br>
	 * 如果非空，记录记录从当前ViewGroup的父AdapterView到当前ViewGroup的所有中间的ViewGroup.(其中包括第一个AdpaterView)<br>
	 * 不包括AdpaterView里面还包含AdapterView的情况，这样生成的测试代码就会有问题
	 */
	public void setListenersForViewGroup(ViewGroup paramViewGroup, boolean paramBoolean, Set paramSet1, Set paramSet2, PreViewManager preViewManager){
		for(int k = 0; k < paramViewGroup.getChildCount(); k++){
			this.viewsCount += 1;
			View view = paramViewGroup.getChildAt(k);
			paramSet1.add(Integer.valueOf(view.hashCode()));
			//如果view的父view是AdapterView或者这个view的父view在这个集合里面，则加到手动更新的集合里面
			if(view.getParent() instanceof AdapterView || 
					(paramSet2.contains(view.getParent().hashCode()))){
				paramSet2.add(view.hashCode());
			}
			//如果preViewIdManager里面保存了AdapterView，需要更新index，这样Listener里面保存的才是正确的index
			if(preViewManager != null && preViewManager.getIdNameList().size() == 1){
				preViewManager.setIndexInAdapterView(view);
			}
			boolean bool = setListenersForSingleView(view, paramBoolean, preViewManager, false);
			if(view.isFocused()){
				this.focusedView = view;
			}
			if(view instanceof ViewGroup){
				if(preViewManager != null){
					//前面有AdapterView，直接插入到列表中
					preViewManager.pushViewIdName(view);
					setListenersForViewGroup((ViewGroup)view, bool, paramSet1, paramSet2, preViewManager);
					preViewManager.popViewIdName();
				}else{
					//如果是AdapterView需要记录，传入递归参数
					if(view instanceof AdapterView){
						preViewManager = new PreViewManager((AdapterView)view);
						setListenersForViewGroup((ViewGroup)view, bool, paramSet1, paramSet2, preViewManager);
						preViewManager = null;
					}else{
						setListenersForViewGroup((ViewGroup)view, bool, paramSet1, paramSet2, null);
					}
				}
			}
		}
	}
	
	public final void onChangeLayout(){
		this.changesInLayout += 1;
		Logger.DEBUG(new StringBuilder("Changes in layout: ").append(this.changesInLayout).toString() + " this.flag = " + this.flag);
	    
		if ((this.changesInLayout == 1) && (this.flag)){
	      this.flag = false;
	      
	      new Timer().schedule(new LayoutChangeTask(), 200L, 200L);
	    }
	}
	
	public final void handleAction(AbstractAction action){
		this.handlerChain.processAction(action);
	}
	
	final class LayoutChangeTask extends TimerTask{
		private int a = 1;
		@Override
		public void run() {
			if(this.a == changesInLayout){
				init();
				
				changesInLayout = 0;
				cancel();
				return;
			}
			this.a = changesInLayout;
		}
		
	}
	final class AddListenersForViewsTask implements Runnable{
		@Override
		public void run() {
		    TreeSet localViewSet = new TreeSet();
		    TreeSet localManualViewSet = new TreeSet();
		    
		    View[] arrayOfView = null;
		    while ((arrayOfView = getWindowDecorViews()) == null) {
		      SystemClock.sleep(100L);
		    }
		    
		    Logger.DEBUG(new StringBuilder("Number of decor views: ").append(arrayOfView.length).toString());
		    
		    viewsCount = 0;
		    
		    if (arrayOfView.length > 0){
		      long l1 = System.currentTimeMillis();
		      
		      for (int i = 0; i < arrayOfView.length; i++){
		    	setListenersForSingleView(arrayOfView[i], true, null, false);
		    	localViewSet.add(Integer.valueOf(arrayOfView[i].hashCode()));
		        
		        viewsCount++;
		        
		        if ((arrayOfView[i] instanceof ViewGroup)) {
		        	setListenersForViewGroup((ViewGroup)arrayOfView[i], true, localViewSet, localManualViewSet, null);
		        }
		      }
		      viewsSet = localViewSet;
		      manualChangeViewSet = localManualViewSet;
		      
		      long l2 = System.currentTimeMillis() - l1;
		      Logger.DEBUG(String.format("Visited %d views in %d ms", new Object[] { Integer.valueOf(viewsCount), Long.valueOf(l2) }));
		      return;
		    }
		    Logger.ERROR("No one decor view found!");
		}
		
	}
	
	static{
		try{
			if (Build.VERSION.SDK_INT >= 17){
				windowManagerName = "sDefaultWindowManager";
				windowManager = Class.forName("android.view.WindowManagerGlobal");
			}
			else if (Build.VERSION.SDK_INT >= 13){
				windowManagerName = "sWindowManager";
				windowManager = Class.forName("android.view.WindowManagerImpl");
			}
			else{
				windowManagerName = "mWindowManager";
				windowManager = Class.forName("android.view.WindowManagerImpl"); 
			}
	    }catch (ClassNotFoundException localClassNotFoundException){
	    	Logger.ERROR("Couldn't find android.view.WindowManagerImpl - fatal!", localClassNotFoundException);
	    }
	    catch (SecurityException localSecurityException){
	    	Logger.ERROR("Couldn't get android.view.WindowManagerImpl", localSecurityException);
	    }
	}
	private static View[] getWindowDecorViews(){
	    try{
			Field viewsField = windowManager.getDeclaredField("mViews");
			Field instanceField = windowManager.getDeclaredField(windowManagerName);
			viewsField.setAccessible(true);
			instanceField.setAccessible(true);
			Object instance = instanceField.get(null);//static field
			View[] result;
			if (android.os.Build.VERSION.SDK_INT >= 19) {
				result = ((ArrayList<View>) viewsField.get(instance)).toArray(new View[0]);
			} else {
				result = (View[]) viewsField.get(instance);
			}
			return result;
	    }
	    catch (SecurityException localSecurityException){
	    	Logger.WARN("Couldn't get decor views", localSecurityException);
	    }
	    catch (NoSuchFieldException localNoSuchFieldException){
	    	Logger.WARN("Couldn't get decor views", localNoSuchFieldException);
	    }
	    catch (IllegalArgumentException localIllegalArgumentException){
	    	Logger.WARN("Couldn't get decor views", localIllegalArgumentException);
	    }
	    catch (IllegalAccessException localIllegalAccessException){
	    	Logger.WARN("Couldn't get decor views", localIllegalAccessException);
	    }
	    return null;
	}
	
}
